package TwoClientGameConnection;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * The TCGServer is a base server for a game built for two players.
 * It allows only two players to connect and facilitates the sending of 
 * messages between them.
 * 
 *  @author Jace Ferguson
 *  @filename TCGServer.java
 */
public class TCGServer implements Runnable{
	private ServerSocket gameServer = null;
	private int gamePort;
	private String host;
	private TCGServerProtocol protocol;
	private TCGServerThread player1 = null;
	private TCGServerThread player2 = null;
	private Integer messageId = 0;
	/**
	 * Constructor that uses a default game port.
	 */
	public TCGServer()
	{
		this.gamePort = 4900;
		
	}

	/**
	 * Constructor that takes a unique game port.
	 * If the game port is outside of the specifications,
	 * it will be set to the default without warning from the server.
	 * @param gamePort	1023 < integer < 65536
	 */
	public TCGServer(int gamePort)
	{
		if(gamePort > 1023 && gamePort < 65536)
		{
			this.gamePort = gamePort;
		}
		else
		{
			this.gamePort = 4900;
		}
	}

	/**
	 * Static function to add a TCGServerThread (which is associated with a player)
	 * to a player member.
	 * @param thread	TCGServerThread object
	 * @return	boolean  true if player was added, false, if all players are full.
	 */
	public boolean addPlayer(TCGServerThread thread)
	{
		if(this.player1 == null)
		{
			this.player1 = thread;
			return true;
		}
		else if(this.player2 == null)
		{
			this.player2 = thread;
			return true;
		}
		return false;

	}
	
	/**
	 * Send a message saying all players have connected.
	 */
	public void sendAllConnected()
	{
		TCGMessage connected = new TCGMessage(this.getMessageId());
		connected.setCommand("playerCheck");
		connected.addParameter("playerCheck", String.valueOf(1));
		this.sendMessageToAllPlayers(connected);
	}
	
	/**
	 * Look up an alias for a given host name
	 * @param host	String	
	 * @return		String the host alias.
	 */
	public String aliasLookup(String host)
	{
		if(this.player1Isset() && this.player1.getHost().equals(host))
		{
			return this.player1.getHostAlias();
		}
		else if(this.player2Isset() && this.player2.getHost().equals(host))
		{
			return this.player2.getHostAlias();
		}
		return "";
	}

	/**
	 * Retrieve the next message id.
	 * @return	Integer message Id.
	 */
	public Integer getMessageId()
	{
		Integer messageId = this.messageId;
		this.incrementMessageCount();
		return messageId;
	}

	/**
	 * Get the player1 object.
	 * @return	TCGServerThread
	 */
	public TCGServerThread getPlayer1()
	{
		return this.player1;
	}

	/**
	 * Get the player2 object.
	 * @return	TCGServerThread
	 */
	public TCGServerThread getPlayer2()
	{
		return this.player2;
	}

	/**
	 * Identify the protocol by name.
	 * @return
	 */
	public String identifyProtocol()
	{
		if(this.protocol != null)
			return this.protocol.getProtocolName();
		else
			return "No Protocol Available";
	}

	/**
	 * Increment the message id count. After 5000 messages, it resets.
	 * Hopefully the server doesn't have more than 5000 active messages.
	 */
	private void incrementMessageCount()
	{
		if(this.messageId == 5000)
		{
			this.messageId = -1;
		}
		++this.messageId;
	}

	/**
	 * Shutdown the server gracefully.
	 */
	public void killServer()
	{
		TCGMessage shutDown = new TCGMessage(this.getMessageId());
		shutDown.setCommand("CLOSECONNECTION");
		shutDown.addParameter("reason", "Server killed by admin.");
		this.sendMessageToAllPlayers(shutDown);
		this.player1 = null;
		this.player2 = null;
		try{
			this.gameServer.close();
		}catch(IOException e)
		{
			//If it can't close the socket, hopefully the OS will after a timeout
			this.gameServer = null;
		}
		catch(NullPointerException e)
		{
			//If it can't close the socket, hopefully the OS will after a timeout
			this.gameServer = null;
		}
	}

	/**
	 * Returns whether or not player1 is connected.
	 * @return boolean
	 */
	public boolean player1Isset()
	{
		if(this.player1 == null)
		{
			return false;
		}
		return true;
	}
	/**
	 * Returns whether or not player2 is connected.
	 * @return
	 */
	public boolean player2Isset()
	{
		if(this.player2 == null)
		{
			return false;
		}
		return true;
	}

	/**
	 * Remove a client that matches the parameter client.
	 * @param thread
	 */
	public void removePlayer(TCGServerThread thread)
	{
		if(this.player1 == thread)
		{
			this.player1 = null;
		}
		else if(this.player2 == thread)
		{
			this.player2 = null;
		}
	}

	/**
	 * Removes a dead player object.
	 * @param player	TCGServerThread
	 */
	public void removeThread(TCGServerThread player)
	{
		this.removePlayer(player);
	}

	/**
	 * Run executes when the thread the server exists in, is started.
	 * Nothing is actually run until you start the game server with
	 * startGameServer();.
	 */
	public void run()
	{
		try{
			this.startGameServer();
		}
		catch(TCGConnectionException e)
		{
			this.protocol.handleError(e);
		}
	}

	/**
	 * Sends an error to the protocol for handling
	 */
	public void sendErrorToProtocol(TCGGeneralException e)
	{
		if(this.protocol != null)
		{
			this.protocol.handleError(e);
		}
	}

	/**
	 * Send a message to all players.
	 * @param hostName
	 * @param message
	 */
	public void sendMessageToAllPlayers(TCGMessage message)
	{
		this.sendMessageToPlayer1(message);
		this.sendMessageToPlayer2(message);
	}

	/**
	 * Send a message to player 1
	 * @param message	TCGMessage message
	 */
	public void sendMessageToPlayer1(TCGMessage message)
	{
		if(this.player1Isset())
		{
			this.player1.sendMessage(message);
		}
	}

	/**
	 * Send a message to player 2
	 * @param message	TCGMessage message
	 */
	public void sendMessageToPlayer2(TCGMessage message)
	{
		if(this.player2Isset())
		{
			this.player2.sendMessage(message);
		}
	}
	
	/**
	 * Send a message to a user defined protocol
	 * @param message	TCGMessage 
	 * @param host	String computer host
	 */
	public void sendMessageToProtocol(TCGMessage message, String host)
	{
		if(this.protocol != null)
		{
			this.protocol.handleMessage(message, host);
		}
	}

	/**
	 * Set the server protocol
	 * @param protocol	TCGServerProtocol
	 */
	public void setProtocol(TCGServerProtocol protocol)
	{
		assert(protocol != null);
		this.protocol = protocol; 
	}

	/**
	 * Starts the game server and waits for connections.
	 * @throws TCGConnectionException
	 */
	public void startGameServer() throws TCGConnectionException
	{
		Socket tempSocket = null;

		//Gets the ip address of the current computer. 
		//There is a TCGGeneralLib file that gets this.
		this.host = TCGGeneralLib.getHostIPAddress();
		if(this.host != "")
		{
			try{
				//Attempt to create the gameServer socket
				this.gameServer = new ServerSocket(this.gamePort);
				while(true) //Must always loop over this once players start to come and go
				{
					while(!this.player1Isset() || !this.player2Isset())
					{
						try{
							tempSocket = this.gameServer.accept();
							TCGServerThread temp; //Storage for the player object
							Thread newPlayer = new Thread(temp = new TCGServerThread(tempSocket, this));
							newPlayer.setDaemon(true);
							newPlayer.start();
							this.addPlayer(temp);
							if(this.player1Isset() && this.player2Isset())
							{
								this.sendAllConnected();
							}
						}
						catch(IOException e)
						{
							this.protocol.handleError(new TCGConnectionException("Unable to accept client.", e));
						}
					}
				}
			}catch(IOException e)
			{
				this.protocol.handleError(new TCGConnectionException("Unable to create the game server.", e));
			}				
		}
		else //No usable host ip address was found.
		{
			this.protocol.handleError(new TCGConnectionException("Unable to create the game server."));
		}
	}
}


